/*
 * Copyright (c) 2009 Charles S. Wilson
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a 
 * copy of this software and associated documentation files (the "Software"), 
 * to deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 * and/or sell copies of the Software, and to permit persons to whom the 
 * Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included 
 * in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 
 * OTHER DEALINGS IN THE SOFTWARE.
 */
#ifdef _MSC_VER
# define _CRT_SECURE_NO_DEPRECATE 1
#endif
#include <stdio.h>
#include <stdlib.h>

/* want windows XP or above */
#define WIN32_LEAN_AND_MEAN
#define _WIN32_WINNT 0x0501
#include <windows.h>
#include <psapi.h>

#ifdef _MSC_VER
# include <direct.h>
# include <process.h>
# include <io.h>
# define setmode _setmode
#else
# include <unistd.h>
# include <stdint.h>
# ifdef __CYGWIN__
#  include <io.h>
# endif
#endif
#include <malloc.h>
#include <stdarg.h>
#include <assert.h>
#include <string.h>
#include <ctype.h>
#ifndef __MINGW32CE__
# include <errno.h>
#endif
#include <fcntl.h>
#include <sys/stat.h>

#include "util.h"
#include "launch.h"

/* declarations of non-ANSI functions */
#if defined(__MINGW32__)
# ifdef __STRICT_ANSI__
int _putenv (const char *);
# endif
#elif defined(__CYGWIN__)
# ifdef __STRICT_ANSI__
char *realpath (const char *, char *);
int putenv (char *);
int setenv (const char *, const char *, int);
# endif
/* #elif defined (other platforms) ... */
#endif

/* portability defines, excluding path handling macros */
#if defined(_MSC_VER)
# define setmode _setmode
# define stat    _stat
# define chmod   _chmod
# define getcwd  _getcwd
# define putenv  _putenv
# define S_IXUSR _S_IEXEC
# ifndef _INTPTR_T_DEFINED
#  define _INTPTR_T_DEFINED
#  define intptr_t int
# endif
#elif defined(__MINGW32__) && !defined(__MINGW32CE__)
# define setmode _setmode
# define stat    _stat
# define chmod   _chmod
# define getcwd  _getcwd
# define putenv  _putenv
#elif defined(__CYGWIN__)
# define HAVE_SETENV
# define FOPEN_WB "wb"
/* #elif defined (other platforms) ... */
#endif

#ifndef S_IXOTH
# define S_IXOTH 0
#endif
#ifndef S_IXGRP
# define S_IXGRP 0
#endif

/* path handling portability macros */
#ifndef DIR_SEPARATOR
# define DIR_SEPARATOR '/'
# define PATH_SEPARATOR ':'
#endif

#if defined (_WIN32) || defined (__MSDOS__) || defined (__DJGPP__) || \
  defined (__OS2__)
# define HAVE_DOS_BASED_FILE_SYSTEM
# define FOPEN_WB "wb"
# ifndef DIR_SEPARATOR_2
#  define DIR_SEPARATOR_2 '\\'
# endif
# ifndef PATH_SEPARATOR_2
#  define PATH_SEPARATOR_2 ';'
# endif
#endif

#ifndef DIR_SEPARATOR_2
# define IS_DIR_SEPARATOR(ch) ((ch) == DIR_SEPARATOR)
#else /* DIR_SEPARATOR_2 */
# define IS_DIR_SEPARATOR(ch) \
        (((ch) == DIR_SEPARATOR) || ((ch) == DIR_SEPARATOR_2))
#endif /* DIR_SEPARATOR_2 */

#ifndef PATH_SEPARATOR_2
# define IS_PATH_SEPARATOR(ch) ((ch) == PATH_SEPARATOR)
#else /* PATH_SEPARATOR_2 */
# define IS_PATH_SEPARATOR(ch) ((ch) == PATH_SEPARATOR_2)
#endif /* PATH_SEPARATOR_2 */

#ifndef FOPEN_WB
# define FOPEN_WB "w"
#endif
#ifndef _O_BINARY
# define _O_BINARY 0
#endif

char *
run2_find_executable (const char *target)
{
  int has_slash = 0;
  const char *p;
  const char *p_next;
  /* static buffer for getcwd */
  char tmp[RUN2_PATHMAX + 1];
  int tmp_len;
  char *concat_name;

  debugMsg (2, "(%s)   : %s", __func__,
            target ? (*target ? target : "EMPTY!") : "NULL!");

  if ((target == NULL) || (*target == '\0'))
    return NULL;

  /* Absolute path? */
#if defined (HAVE_DOS_BASED_FILE_SYSTEM)
  if (isalpha ((unsigned char) target[0]) && target[1] == ':')
    {
      concat_name = run2_strdup (target);
      if (run2_check_executable (concat_name))
	return concat_name;
      free ((void *)concat_name);
    }
  else if (target[0] == '~')
    {
      char *tmp = run2_strdup (target);
      char *s1 = tmp;
      const char *s2;
      char *homedir;
      while (*s1 && !IS_DIR_SEPARATOR(*s1))
        s1++;
      *s1 = '\0';
      homedir = run2_get_homedir (&(tmp[1]));
      free (tmp);
      s2 = target;
      while (*s2 && !IS_DIR_SEPARATOR(*s2))
        s2++;
      concat_name = (char *) run2_malloc (
        (strlen (s2) + strlen (homedir) + 1) * sizeof (char));
      strcpy (concat_name, homedir);
      strcat (concat_name, s2);
      free (homedir);
      if (run2_check_executable (concat_name))
	return concat_name;
      free ((void *)concat_name);
    }
  else
    {
#endif
      if (IS_DIR_SEPARATOR (target[0]))
	{
	  concat_name = run2_strdup (target);
	  if (run2_check_executable (concat_name))
	    return concat_name;
	  free ((void *)concat_name);
	}
#if defined (HAVE_DOS_BASED_FILE_SYSTEM)
    }
#endif

  for (p = target; *p; p++)
    if (*p == '/')
      {
	has_slash = 1;
	break;
      }
  if (!has_slash)
    {
      /* no slashes; search PATH */
      const char *path = getenv ("PATH");
      if (path != NULL)
	{
	  for (p = path; *p; p = p_next)
	    {
	      const char *q;
	      size_t p_len;
	      for (q = p; *q; q++)
		if (IS_PATH_SEPARATOR (*q))
		  break;
	      p_len = q - p;
	      p_next = (*q == '\0' ? q : q + 1);
	      if (p_len == 0)
		{
		  /* empty path: current directory */
		  if (getcwd (tmp, RUN2_PATHMAX) == NULL)
		    run2_error (1, errno, "getcwd failed");
		  tmp_len = strlen (tmp);
		  concat_name = (char *) run2_malloc (
                    (tmp_len + 1 + strlen (target) + 1) * sizeof(char));
		  memcpy (concat_name, tmp, tmp_len);
		  concat_name[tmp_len] = '/';
		  strcpy (concat_name + tmp_len + 1, target);
		}
	      else
		{
		  concat_name = (char *) run2_malloc (
		    (p_len + 1 + strlen (target) + 1) * sizeof(char));
		  memcpy (concat_name, p, p_len);
		  concat_name[p_len] = '/';
		  strcpy (concat_name + p_len + 1, target);
		}
	      if (run2_check_executable (concat_name))
		return concat_name;
	      free ((void *)concat_name);
	    }
	}
      /* not found in PATH; assume curdir */
    }
  /* Relative path | not found in path: prepend cwd */
  if (getcwd (tmp, RUN2_PATHMAX) == NULL)
    run2_error (1, errno, "getcwd failed");
  tmp_len = strlen (tmp);
  concat_name = (char *) run2_malloc (
    (tmp_len + 1 + strlen (target) + 1) * sizeof(char));
  memcpy (concat_name, tmp, tmp_len);
  concat_name[tmp_len] = '/';
  strcpy (concat_name + tmp_len + 1, target);

  if (run2_check_executable (concat_name))
    return concat_name;
  free ((void *)concat_name);
  return NULL;
}

char *
run2_find_directory (const char *target)
{
  int has_slash = 0;
  const char *CWD = ".";
  const char *p;
  const char *p_next;
  /* static buffer for getcwd */
  char tmp[RUN2_PATHMAX + 1];
  int tmp_len;
  char *concat_name;

  debugMsg (2, "(%s)   : %s", __func__,
	    target ? (*target ? target : "EMPTY!") : "NULL!");

  if (!target || !*target)
    target = CWD;

  /* Absolute path? */
#if defined (HAVE_DOS_BASED_FILE_SYSTEM)
  if (isalpha ((unsigned char) target[0]) && target[1] == ':')
    {
      concat_name = run2_strdup (target);
      if (run2_check_directory (concat_name))
	return concat_name;
      free ((void *)concat_name);
    }
  else if (target[0] == '~')
    {
      char *tmp = run2_strdup (target);
      char *s1 = tmp;
      const char *s2;
      char *homedir;
      while (*s1 && !IS_DIR_SEPARATOR(*s1))
        s1++;
      *s1 = '\0';
      homedir = run2_get_homedir (&(tmp[1]));
      free (tmp);
      s2 = target;
      while (*s2 && !IS_DIR_SEPARATOR(*s2))
        s2++;
      concat_name = (char *) run2_malloc (
        (strlen (s2) + strlen (homedir) + 1) * sizeof (char));
      strcpy (concat_name, homedir);
      strcat (concat_name, s2);
      free (homedir);
      if (run2_check_directory (concat_name))
	return concat_name;
      free ((void *)concat_name);
    }
  else
    {
#endif
      if (IS_DIR_SEPARATOR (target[0]))
	{
	  concat_name = run2_strdup (target);
	  if (run2_check_directory (concat_name))
	    return concat_name;
	  free ((void *)concat_name);
	}
#if defined (HAVE_DOS_BASED_FILE_SYSTEM)
    }
#endif

  /* Relative path: prepend cwd */
  if (getcwd (tmp, RUN2_PATHMAX) == NULL)
    run2_error (1, errno, "getcwd failed");
  tmp_len = strlen (tmp);
  concat_name = (char *) run2_malloc (
    (tmp_len + 1 + strlen (target) + 1) * sizeof(char));
  memcpy (concat_name, tmp, tmp_len);
  concat_name[tmp_len] = '/';
  strcpy (concat_name + tmp_len + 1, target);

  if (run2_check_directory (concat_name))
    return concat_name;
  free ((void *)concat_name);
  return NULL;
}

int
run2_check_executable (const char *path)
{
  struct stat st;
  debugMsg (2, "(%s)  : %s", __func__,
            path ? (*path ? path : "EMPTY!") : "NULL!");

  if ((!path) || (!*path))
    return 0;

  if ((stat (path, &st) >= 0)
      && (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
    return 1;
  else
    return 0;
}

int
run2_check_directory (const char *path)
{
  struct stat st;
  debugMsg (2, "(%s)  : %s", __func__,
            path ? (*path ? path : "EMPTY!") : "NULL!");

  if ((!path) || (!*path))
    return 0;

  if (stat (path, &st) < 0)
    return 0;

  /* other tests for access to a directory? */
  return 1;
}

int
run2_make_executable (const char *path)
{
  int rval = 0;
  struct stat st;
  debugMsg (2, "(%s)   : %s", __func__,
            path ? (*path ? path : "EMPTY!") : "NULL!");

  if ((!path) || (!*path))
    return 0;

  if (stat (path, &st) >= 0)
    {
      rval = chmod (path, st.st_mode | S_IXOTH | S_IXGRP | S_IXUSR);
    }
  return rval;
}

/* Searches for the full path of the target.  Returns
   newly allocated full path name if found, NULL otherwise
*/
char *
run2_chase_symlinks (const char *pathspec)
{
#ifndef S_ISLNK
  return run2_strdup (pathspec);
#else
  char buf[RUN2_PATHMAX];
  struct stat s;
  char *tmp_pathspec = run2_strdup (pathspec);
  char *p;
  int has_symlinks = 0;
  while (strlen (tmp_pathspec) && !has_symlinks)
    {
      debugMsg (3, "(%s) checking path component for symlinks: %s",
                __func__, tmp_pathspec);
      if (lstat (tmp_pathspec, &s) == 0)
	{
	  if (S_ISLNK (s.st_mode) != 0)
	    {
	      has_symlinks = 1;
	      break;
	    }

	  /* search backwards for last DIR_SEPARATOR */
	  p = tmp_pathspec + strlen (tmp_pathspec) - 1;
	  while ((p > tmp_pathspec) && (!IS_DIR_SEPARATOR (*p)))
	    p--;
	  if ((p == tmp_pathspec) && (!IS_DIR_SEPARATOR (*p)))
	    {
	      /* no more DIR_SEPARATORS left */
	      break;
	    }
	  *p = '\0';
	}
      else
	{
#ifndef __MINGW32CE__
	  char *errstr = strerror (errno);
	  run2_error (1, errno, "Error accessing file %s (%s)", tmp_pathspec, errstr);
#else
	  run2_error (1, errno, "Error accessing file %s", tmp_pathspec);
#endif
	}
    }
  free ((void *)tmp_pathspec);

  if (!has_symlinks)
    {
      return run2_strdup (pathspec);
    }

  tmp_pathspec = realpath (pathspec, buf);
  if (tmp_pathspec == 0)
    {
      run2_error (1, errno, "Could not follow symlinks for %s", pathspec);
    }
  return run2_strdup (tmp_pathspec);
#endif
}

char *
run2_strendzap (char *str, const char *pat)
{
  size_t len, patlen;

  assert (str != NULL);
  assert (pat != NULL);

  len = strlen (str);
  patlen = strlen (pat);

  if (patlen <= len)
    {
      str += len - patlen;
      if (strcmp (str, pat) == 0)
	*str = '\0';
    }
  return str;
}

/* Quotes an argument vector before constructing a command string
   suitable for CreateProcess (in the same way that spawn() does:
   concatenate the arguments, separated by ' ', without calling
   the command interpreter:
     (getenv ("COMSPEC") != NULL ? getenv ("COMSPEC") :
      ({ OSVERSIONINFO v; v.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
         GetVersionEx(&v);
         v.dwPlatformId == VER_PLATFORM_WIN32_NT;
      }) ? "cmd.exe" : "command.com").
   We must quote the arguments since Win32 CreateProcess() interprets
   characters like ' ', '\t', '\\', '"' (but not '<' and '>') in a
   special way:
   - Space and tab are interpreted as delimiters. They are not treated as
     delimiters if they are surrounded by double quotes: "...".
   - Unescaped double quotes are removed from the input. Their only effect is
     that within double quotes, space and tab are treated like normal
     characters.
   - Backslashes not followed by double quotes are not special.
   - But 2*n+1 backslashes followed by a double quote become
     n backslashes followed by a double quote (n >= 0):
       \" -> "
       \\\" -> \"
       \\\\\" -> \\"
 */
#define SHELL_SPECIAL_CHARS "\"\\ \001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037"
#define SHELL_SPACE_CHARS " \001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037"
char **
run2_quote_argv (char **argv)
{
  size_t argc;
  char **new_argv;
  size_t i;

  /* Count number of arguments.  */
  for (argc = 0; argv[argc] != NULL; argc++)
    ;

  /* Allocate new argument vector.  */
  new_argv = (char **) run2_malloc ((argc + 1) * sizeof(char *));

  /* Put quoted arguments into the new argument vector.  */
  for (i = 0; i < argc; i++)
    {
      const char *string = argv[i];

      if (string[0] == '\0')
	new_argv[i] = run2_strdup ("\"\"");
      else if (strpbrk (string, SHELL_SPECIAL_CHARS) != NULL)
	{
	  int quote_around = (strpbrk (string, SHELL_SPACE_CHARS) != NULL);
	  size_t length;
	  unsigned int backslashes;
	  const char *s;
	  char *quoted_string;
	  char *p;

	  length = 0;
	  backslashes = 0;
	  if (quote_around)
	    length++;
	  for (s = string; *s != '\0'; s++)
	    {
	      char c = *s;
	      if (c == '"')
		length += backslashes + 1;
	      length++;
	      if (c == '\\')
		backslashes++;
	      else
		backslashes = 0;
	    }
	  if (quote_around)
	    length += backslashes + 1;

	  quoted_string = (char *) run2_malloc ((length + 1) * sizeof(char));

	  p = quoted_string;
	  backslashes = 0;
	  if (quote_around)
	    *p++ = '"';
	  for (s = string; *s != '\0'; s++)
	    {
	      char c = *s;
	      if (c == '"')
		{
		  unsigned int j;
		  for (j = backslashes + 1; j > 0; j--)
		    *p++ = '\\';
		}
	      *p++ = c;
	      if (c == '\\')
		backslashes++;
	      else
		backslashes = 0;
	    }
	  if (quote_around)
	    {
	      unsigned int j;
	      for (j = backslashes; j > 0; j--)
		*p++ = '\\';
	      *p++ = '"';
	    }
	  *p = '\0';

	  new_argv[i] = quoted_string;
	}
      else
	new_argv[i] = run2_strdup (string);
    }
  new_argv[argc] = NULL;

  return new_argv;
}


